#pragma once
#include <iostream>
#include "Socket.hpp"
#include <memory>
#include <poll.h>
// TCP Sever
static const int gsize = sizeof(fd_set) * 8;
static const int gdefaultfd = -1;
class PollServer
{
public:
    void accept()
    {
        LOG(LogLevel::INFO) << " aceept success";
        InetAddr client;
        // 这里的Accept不会被阻塞
        int sockfd = _listenSocket->Accept(&client);
        if (sockfd > 0)
        {
            LOG(LogLevel::DEBUG) << "获取一个新连接: " << client.ToString() << " fd " << sockfd;
            // 这里需要把文件描述符托管给selet,然后下一次循环会重新添加到selet中
            for (int pos = 0; pos < gsize; pos++)
            {
                if (fd_arry[pos].fd == gdefaultfd)
                {
                    fd_arry[pos].fd = sockfd;
                    break;
                }
            }
        }
    }
    void recver(int pos)
    {
        int fd = fd_arry[pos].fd;
        char buffer[1024];
        // 到这里的读一定不会是阻塞的
        int n = recv(fd, &buffer, sizeof(buffer), 0);
        // 记住这里的读是不完成，因为TCP是面向字节流的，还需要引入协议，然后进行序列和反序例化，这一点后面会epoll写一个完整的代码引入
        if (n > 0)
        {
            buffer[n] = 0;
            std::string message = "Server echo #";
            message += buffer;
            send(fd, message.c_str(), message.size(), 0);
        }
        else if (n == 0)
        {
            // 这里表示读端关闭了
            LOG(LogLevel::WARNING) << "对端关闭";
            fd_arry[pos].fd = gdefaultfd;
            fd_arry[pos].events = fd_arry[pos].revents = 0;
            close(fd); // 然后关闭文件描述符
        }
        else
        {
            LOG(LogLevel::ERROR) << "读取失败";
            fd_arry[pos].fd = gdefaultfd;
            fd_arry[pos].events = fd_arry[pos].revents = 0;
            close(fd); // 然后关闭文件描述符
        }
    }
    void handler()
    {
        sleep(3);
        for (int pos = 0; pos < gsize; pos++)
        {
            if (fd_arry[pos].fd == gdefaultfd)
            {
                continue;
            }
            // 到这里说明这里的文件描述符一定是我们关心的
            if (fd_arry[pos].revents & POLLIN)
            {
                // 到这里说明这里的文件描述符一定是我们关心的并且已经就绪的
                // 如果是listen套接字就绪
                if (pos == 0)
                {
                    accept();
                }
                else
                {
                    // 这里一定是读文件就绪，并且不是listen套接字
                    recver(pos);
                }
            }
        }
        // 这里为什么不能fork,因为这是一个单进程的程序，如果fork了这里子进程会继续进行fork之后的代码，子进程和父进程都会进入Run中的while
        // 然后会一直运行，并且报错
        //  if (fork() == 0)
        //  {
        //      _listenSocket->Close();
        //  }
    }

public:
    PollServer(int port)
        : _listenSocket(std::make_unique<TcpSocket>()),
          _port(port)
    {
        _listenSocket->BuildListenSocketMethod(_port);
        for (int i = 0; i < gsize; i++)
        {
            fd_arry[i].fd = gdefaultfd;
        }
        fd_arry[0].fd = _listenSocket->SockFd();
        fd_arry[0].events = POLLIN; // 这里表示我们关心读事件
    }
    void Init()
    {
    }
    void Run()
    {

        while (true)
        {
            int timeout = 1000;
            int n = poll(fd_arry, gsize, timeout);
            switch (n)
            {
            case 0:
                LOG(LogLevel::DEBUG) << "time out ";
                break;
            case -1:
                LOG(LogLevel::ERROR) << " poll error";

            default:
                // 一旦就绪了，就是要派发给不同的模块，所以我们更喜欢叫事件派发器
                handler();
                break;
            }
            // 所以随着新链接的到来上面的switch函数已经不适用，现在需要分辨是什么套接字，是监听套接字还是其他的套接字
            // 不同的套接字需要不同的处理方式。
        }
    }
    ~PollServer()
    {
    }

private:
    std::unique_ptr<Socket> _listenSocket;
    int _port;
    struct pollfd fd_arry[gsize];
};
